package com.lyw;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.nio.file.*;

import ai.djl.*;
import ai.djl.basicdataset.cv.classification.Mnist;
import ai.djl.ndarray.types.*;
import ai.djl.ndarray.types.Shape;
import ai.djl.training.*;
import ai.djl.training.dataset.*;
import ai.djl.training.initializer.*;
import ai.djl.training.loss.*;
import ai.djl.training.listener.*;
import ai.djl.training.evaluator.*;
import ai.djl.training.optimizer.*;
import ai.djl.training.util.*;
import ai.djl.basicmodelzoo.cv.classification.*;
import ai.djl.basicmodelzoo.basic.*;
import ai.djl.translate.TranslateException;
import ai.onnxruntime.OnnxTensor;
import ai.onnxruntime.OrtEnvironment;
import ai.onnxruntime.OrtException;
import ai.onnxruntime.OrtSession;
import com.alibaba.fastjson.JSONArray;
import com.lyw.game.Initialize;
import com.lyw.utils.BaseAddressUtil;
import com.lyw.utils.JavaCvUtil;
import com.lyw.utils.OpenCvUtil;
import com.lyw.yolo.YoloV7;
import org.bytedeco.opencv.opencv_core.RectVector;
import org.bytedeco.opencv.opencv_objdetect.CascadeClassifier;
import org.opencv.core.*;
import org.opencv.features2d.DescriptorMatcher;
import org.opencv.features2d.Features2d;
import org.opencv.features2d.ORB;
import org.opencv.features2d.SIFT;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.videoio.VideoCapture;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.io.IOException;
import java.util.*;
import java.util.List;
import ai.djl.*;
import ai.djl.nn.*;
import ai.djl.nn.core.*;
import ai.djl.training.*;
import org.springframework.context.ConfigurableApplicationContext;

import javax.imageio.ImageIO;

import static com.lyw.predictTest.*;


@SpringBootApplication
public class Application {
    // 我们将使用的数据集是MNIST，这是一个手写数字数据库。每个图像在 28x28 图像中包含从 0 到 9 的黑白数字。
    // 它通常在深度学习入门时使用，因为它体积小且训练速度快。
    static long inputSize = 28*28; // 图片像素
    static long outputSize = 10; // 数字个数
    public static final String matches = "C:\\Users\\Administrator\\Desktop\\TPlan\\match";
    /*private static Signature dbl(Ops tf) {
        Placeholder<TInt32> x = tf.placeholder(TInt32.class);
        Add<TInt32> dblX = tf.math.add(x, x);
        return Signature.builder().input("x", x).output("dbl", dblX).build();
    }*/
    public static void main2(String[] args) throws IOException, TranslateException {
        // 在图像分类中，输入是单个图像，并且根据图像的主要主题将其分类为许多不同的可能类别。图像的类别取决于您正在训练的特定数据。
        ai.djl.Application application = ai.djl.Application.CV.IMAGE_CLASSIFICATION;
        // 块的用途类似于将输入转换NDList为输出的函数NDList
        // 块的特殊之处在于它们包含许多在其功能中使用的参数，并在深度学习过程中进行训练。
        // 这是一个容器块，其子块形成块链，其中每个子块将其输出按顺序馈送到下一个子块。
        SequentialBlock block = new SequentialBlock();
        // 第一层和最后一层具有固定的大小，具体取决于您所需的输入和输出大小。但是，您可以自
        // 由选择网络中中间层的数量和大小。我们将创建一个更小的 MLP，其中有两个中间层，逐渐
        // 减小尺寸。通常，您会尝试不同的值，看看什么最适合您的数据集。
        block.add(Blocks.batchFlattenBlock(inputSize));
        block.add(Linear.builder().setUnits(128).build());
        block.add(Activation::relu);
        block.add(Linear.builder().setUnits(64).build());
        block.add(Activation::relu);
        block.add(Linear.builder().setUnits(outputSize).build());
        /*
        SequentialBlock {
            batchFlatten
            Linear
            LambdaBlock
            Linear
            LambdaBlock
            Linear
        }
        */
        // 现在您已经成功创建了第一个神经网络，您可以使用该网络来训练您的模型。
        // 第 1 步：准备 MNIST 数据集进行训练
        // 为了进行训练，您必须创建一个 Dataset 类来包含您的训练数据。数据集是
        // 神经网络所表示的函数的样本输入/输出对的集合。每个单个输入/输出都由一
        // 个Record表示。每个记录可以有多个输入或输出数组，例如图像问答数据集，
        // 其中输入既是图像也是关于图像的问题，而输出是问题的答案。
        //  由于数据学习是高度可并行的，因此训练通常不是一次使用一条记录，而是使用
        // 一个Batch来完成。这可以显着提高性能，尤其是在处理图像时

        // 采样器
        // 然后，我们必须决定从数据集中加载数据的参数。MNIST 所需的唯一参数是Sampler的选择。
        // 采样器在迭代每个批次时决定数据集中的哪些元素以及有多少元素属于每个批次。我们将让它
        // 随机打乱批次的元素，并使用 32 的批量大小。批量大小通常是内存中适合 2 次方幂。
        int batchSize = 32;
        Mnist mnist = Mnist.builder().setSampling(batchSize, true).build();
        mnist.prepare(new ProgressBar());

        // 第 2 步：创建模型
        // 模型包含一个神经网络块以及用于训练过程的其他工件。
        // 它拥有有关您将使用的输入、输出、形状和数据类型的附加信息。
        // 由于 MNIST 数据集中的图像是 28x28 灰度图像，因此我们将创建一个
        // 具有 28 x 28 输入的 MLP 块。输出将为 10，因为每个图像有 10 个可
        // 能的类别（0 到 9）。new int[] {128, 64}对于隐藏层，我们通过尝试不同的值来选择。
        Model model = Model.newInstance("mlp");
        model.setBlock(new Mlp(28 * 28, 10, new int[] {128, 64}));
        // 第 3 步：创建培训师
        // 您可以创建一个Trainer来训练您的模型。培训师是协调培训过程的主要班级。通常，它们将使用资源尝试打开，并在训练结束后关闭。
        // 训练器采用现有模型并尝试优化模型块内的参数以最好地匹配数据集。大多数优化基于随机梯度下降(SGD)。
        // 步骤 3.1：设置您的训练配置
        // 以下是配置训练时可能需要的一些常见项目：
        //
        //必需 Loss函数：损失函数用于衡量我们的模型与数据集的匹配程度。由于该函数的值越低越好，因此称为“损失”函数。损失是模型唯一必需的参数
        //Evaluator函数：评估器函数还用于衡量我们的模型与数据集的匹配程度。与损失不同，它们仅供人们查看，并不用于优化模型。由于许多损失并不那么直观，因此添加其他评估器（例如准确度）可以帮助您了解模型的表现。如果您知道任何有用的评估器，我们建议添加它们。
        //Training Listeners：训练监听器通过监听器接口向训练过程添加附加功能。这可以包括显示训练进度、在训练未定义时提前停止或记录性能指标。我们提供了几组简单的默认侦听器。
        //您还可以配置其他选项，例如设备、初始化程序和优化程序。查看更多详情。

        DefaultTrainingConfig config = new DefaultTrainingConfig(Loss.softmaxCrossEntropyLoss())
                // softmaxCrossEntropyLoss是分类问题的标准损失
                .addEvaluator(new Accuracy()) // 使用准确性，这样我们人类就可以了解模型的准确性
                .addTrainingListeners(TrainingListener.Defaults.logging());

        // 现在我们有了训练配置，我们应该为我们的模型创建一个新的训练器
        Trainer trainer = model.newTrainer(config);

        // 第 5 步：初始化训练
        // 在训练模型之前，您必须使用起始值初始化所有参数。您可以通过传入输入形状来使用训练器进行此初始化。
        // 输入形状的第一个轴是批量大小。这不会影响参数初始化，所以这里可以使用1。
        // MLP 输入形状的第二个轴 - 输入图像中的像素数。
        trainer.initialize(new Shape(1, 28 * 28));

        // 第 6 步：训练模型
        // 训练时，通常将其组织为纪元，每个纪元在数据集中的每个项目上训练模型一次。它比随机训练稍快一些。
        // 然后，我们将使用 EasyTrain，正如其名称所承诺的那样，使训练变得简单。如果您想了解有关训练循
        // 环如何工作的更多详细信息，请参阅 EasyTrain 课程或阅读我们的《深度学习》一书。
        // 深度学习通常在epoch中进行训练，每个epoch对数据集中的每个项目训练模型一次
        int epoch = 2; // 时代，纪元；值得纪念的事件（或日期）；世（地质年代，纪下分世）；历元
        EasyTrain.fit(trainer, epoch, mnist, null);

        // 第 7 步：保存模型
        Path modelDir = Paths.get("build/mlp");
        Files.createDirectories(modelDir);
        model.setProperty("Epoch", String.valueOf(epoch));
        model.save(modelDir, "mlp");
    }
    public static float[] convertBufferedImageToONNXInput(BufferedImage image) {
        // 获取图像宽度和高度
        int width = image.getWidth();
        int height = image.getHeight();

        // 获取像素数据
        Raster raster = image.getRaster();
        int[] pixels = new int[width * height * 3];
        raster.getPixels(0, 0, width, height, pixels);

        // 将像素值转换为浮点数并进行归一化
        float[] normalizedPixels = new float[pixels.length];
        for (int i = 0; i < pixels.length; i++) {
            // 假设图像是 RGB 图像，这里只处理红色通道作为示例
            int red = (pixels[i] >> 16) & 0xFF;
            normalizedPixels[i] = red / 255.0f; // 归一化到 0-1 范围
        }

        return normalizedPixels;
    }
    public static void main(String[] args) throws IOException, OrtException {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        System.setProperty("java.awt.headless", "false");
        /*String pic = "C:\\Users\\Administrator\\Desktop\\123.png";
        Mat src = Imgcodecs.imread(pic);
        int srcw = src.width();
        int srch = src.height();
        Mat dst = resizeWithPadding(src);
        OnnxTensor tensor = transferTensor(dst);
        OrtSession.Result result = session.run(Collections.singletonMap("images", tensor));
        OnnxTensor res = (OnnxTensor)result.get(0);
        float[][][] dataRes = (float[][][])res.getValue();
        float[][] data = dataRes[0];
        JSONArray srcRec = filterRec1(data);
        JSONArray srcRec2 = filterRec2(srcRec);
        JSONArray dstRec = transferSrc2Dst(srcRec2,srcw,srch);
        pointBox(pic,dstRec);*/
        SpringApplication.run(Application.class, args);
//        JavaCvUtil.test();
//        YoloV7.main1();
//        OpenCvUtil.handlePicSize();
       /* System.setProperty("java.awt.headless", "false");
//
        OpenCvUtil.preparePng();
//        OpenCvUtil.resize();
//        OpenCvUtil.loadConfig();
        /*System.out.println("Hello TensorFlow " + TensorFlow.version());
        try (ConcreteFunction dbl = ConcreteFunction.create(Application::dbl);
             TInt32 x = TInt32.scalarOf(10);
             Tensor dblX = dbl.call(x)) {
            System.out.println(x.getInt() + " doubled is " + ((TInt32)dblX).getInt());
        } */
//        BaseAddressUtil.convertBaseAddress("C:\\Users\\Administrator\\Desktop\\TPlan\\TPlan\\src\\main\\resources\\2024年01月13日创新中心(易)基址.txt");
        /*System.setProperty("java.awt.headless", "false");
        SpringApplication application = new SpringApplication(Application.class);
        // 禁止打印banner
        application.setBannerMode(Banner.Mode.OFF);

        ConfigurableApplicationContext applicationContext = application.run(args);

        // 注册关闭钩子
        Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("服务关闭")));

        // 从容器获取实例对象
        Initialize initialize = applicationContext.getBean(Initialize.class);
        initialize.Init();*/
//        test();
//        OpenCvUtil.matchTemplate(new File("C:\\Users\\Administrator\\Desktop\\template123.png"));
//        OpenCvUtil.test1("C:\\Users\\Administrator\\Desktop\\TPlan\\TPlan\\src\\main\\resources\\images\\test\\template.png",
//        OpenCvUtil.test1("C:\\Users\\Administrator\\Desktop\\TPlan\\TPlan\\src\\main\\resources\\images\\test\\2.png",
//                "C:\\Users\\Administrator\\Desktop\\TPlan\\TPlan\\src\\main\\resources\\images\\test\\1.png");
        /*OpenCvUtil.preparePng();
        OpenCvUtil.findMateInfo(
                "C:\\Users\\Administrator\\Desktop\\test\\match\\4.png");*/
    }
    public static void test() {
    }
    public static void test11() {
        // 加载原始图像
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        System.setProperty("java.awt.headless", "false");
        // 加载原始图像和模板图像列表

        String origin = "C:\\Users\\Administrator\\Desktop\\TPlan\\TPlan\\origin.jpg";
        String templ = "C:\\Users\\Administrator\\Desktop\\TPlan\\TPlan\\src\\main\\resources\\images\\test\\template.png";

        Mat src = Imgcodecs.imread(origin);
        Mat dst = Imgcodecs.imread(templ);
        SIFT fd = SIFT.create();
        DescriptorMatcher Matcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE_L1);

        MatOfKeyPoint mkp = new MatOfKeyPoint();
        fd.detect(src, mkp);
        Mat desc = new Mat();
        fd.compute(src, mkp, desc);
        Features2d.drawKeypoints(src, mkp, src);

        MatOfKeyPoint mkp2 = new MatOfKeyPoint();
        fd.detect(dst, mkp2);
        Mat desc2 = new Mat();
        fd.compute(dst, mkp2, desc2);
        Features2d.drawKeypoints(dst, mkp2, dst);


        // Matching features
        MatOfDMatch Matches = new MatOfDMatch();
        Matcher.match(desc, desc2, Matches);

        List<DMatch> l = Matches.toList();
        List<DMatch> goodMatch = new ArrayList<DMatch>();
        for (int i = 0; i < l.size(); i++) {
            DMatch dmatch = l.get(i);
            if (Math.abs(dmatch.queryIdx - dmatch.trainIdx) < 10f) {
                goodMatch.add(dmatch);
            }

        }

        Matches.fromList(goodMatch);
        // Show result
        Mat OutImage = new Mat();
        Features2d.drawMatches(src, mkp, dst, mkp2, Matches, OutImage);
        HighGui.imshow("a", OutImage);
        HighGui.waitKey();
    }
    public static void test12() {
        // 加载原始图像
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        System.setProperty("java.awt.headless", "false");
        // 加载原始图像和模板图像列表

        String origin = "C:\\Users\\Administrator\\Desktop\\a.png";
//        String origin = "C:\\Users\\Administrator\\Desktop\\TPlan\\TPlan\\src\\main\\resources\\images\\other\\output.jpg";
        String templ = "C:\\Users\\Administrator\\Desktop\\b.png";
//        String templ = "C:\\Users\\Administrator\\Desktop\\TPlan\\TPlan\\scale.jpg";

        Mat src = Imgcodecs.imread(origin, 1);
        Mat dst = Imgcodecs.imread(templ, 1);
        Mat mat1_gray = new Mat();
        Imgproc.cvtColor(src, mat1_gray, Imgproc.COLOR_BGR2GRAY);
        Mat mat2_gray = new Mat();
        Imgproc.cvtColor(dst, mat2_gray, Imgproc.COLOR_BGR2GRAY);
        mat1_gray.convertTo(mat1_gray, CvType.CV_32F);
        mat2_gray.convertTo(mat2_gray, CvType.CV_32F);
        double result = Imgproc.compareHist(mat1_gray, mat2_gray, Imgproc.CV_COMP_CORREL);
        if (result == 1) {
            // 此处结果为1则为完全相同
            return;
        }
        System.out.println("相似度数值为:" + result);
        Mat mat_result = new Mat();
        //计算两个灰度图的绝对差值，并输出到一个Mat对象中
        Core.absdiff(mat1_gray, mat2_gray, mat_result);
        //将灰度图按照阈值进行绝对值化
        mat_result.convertTo(mat_result, CvType.CV_8UC1);
        List<MatOfPoint> mat2_list = new ArrayList<MatOfPoint>();
        Mat mat2_hi = new Mat();
        //寻找轮廓图
        Imgproc.findContours(mat_result, mat2_list, mat2_hi, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
        Mat mat_result1 = src;
        Mat mat_result2 = dst;
        //使用红色标记不同点
        System.out.println(mat2_list.size());

        for (MatOfPoint matOfPoint : mat2_list) {
            Rect rect = Imgproc.boundingRect(matOfPoint);
            if (rect.width > 48) {
                System.out.println(rect.toString());
                Imgproc.rectangle(mat_result1, rect.tl(), rect.br(), new Scalar(0, 0, 255), 2);
                Imgproc.rectangle(mat_result2, rect.tl(), rect.br(), new Scalar(0, 0, 255), 2);
            }
        }

        Imgcodecs.imwrite("1.png", mat_result1);
        Imgcodecs.imwrite("2.png", mat_result2);






        ORB orb = ORB.create();
        DescriptorMatcher Matcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE);

        MatOfKeyPoint keyPoint1 = new MatOfKeyPoint();
        MatOfKeyPoint keyPoint2 = new MatOfKeyPoint();
        Mat desc1 = new Mat();
        Mat desc2 = new Mat();
        orb.detect(src, keyPoint1, new Mat());
        orb.detect(dst, keyPoint2, new Mat());

        orb.compute(src, keyPoint1, desc1);
        orb.compute(dst, keyPoint2, desc2);

        MatOfDMatch matches = new MatOfDMatch();
        Matcher.match(desc1, desc2, matches);

        DMatch[] dMatches = matches.toArray();
        float max = dMatches[0].distance;
        float min = dMatches[1].distance;
        List<DMatch> dMatches1 = matches.toList();
        for (int i = 0; i < dMatches1.size(); i++) {
            float distance = dMatches1.get(i).distance;
            if (distance > max) {
                max = distance;
            } else {
                min = distance;
            }
        }


        System.out.println();
/*
        // Matching features

        MatOfDMatch Matches = new MatOfDMatch();
        Matcher.match(desc, desc2, Matches);

        double maxDist = Double.MIN_VALUE;
        double minDist = Double.MAX_VALUE;

        DMatch[] mats = Matches.toArray();
        for (int i = 0; i < mats.length; i++) {
            double dist = mats[i].distance;
            if (dist < minDist) {
                minDist = dist;
            }
            if (dist > maxDist) {
                maxDist = dist;
            }
        }
        System.out.println("Min Distance:" + minDist);
        System.out.println("Max Distance:" + maxDist);
        List<DMatch> goodMatch = new LinkedList<>();

        for (int i = 0; i < mats.length; i++) {
            double dist = mats[i].distance;
            if (dist < 3 * minDist && dist < 0.2f) {
                goodMatch.add(mats[i]);
            }
        }

        Matches.fromList(goodMatch);
        // Show result
        Mat OutImage = new Mat();
        Features2d.drawMatches(src, mkp, dst, mkp2, Matches, OutImage);

        HighGui.imshow("E:/work/qqq/Y4.jpg", OutImage);*/
    }
    public static void test1() {
        // 创建VideoCapture对象，打开视频流
        VideoCapture capture = new VideoCapture(800, 600); // 0表示从默认摄像头捕获，也可以指定其他摄像头编号

        // 检查是否成功打开视频流
        if (!capture.isOpened()) {
            System.out.println("Failed to open video stream.");
            return;
        }

        // 创建窗口，用于显示视频流和检测结果
        String windowName = "Color Detection";
        HighGui.namedWindow(windowName);

        while (true) {
            // 读取帧
            Mat frame = new Mat();
            capture.read(frame);

            // 将帧转换为HSV色彩空间
            Mat hsvFrame = new Mat();
            Imgproc.cvtColor(frame, hsvFrame, Imgproc.COLOR_BGR2HSV);

            // 定义颜色范围（以蓝色为例）
            Scalar lowerBlue = new Scalar(110, 50, 50);
            Scalar upperBlue = new Scalar(130, 255, 255);

            // 进行色彩分类，生成掩码图像
            Mat mask = new Mat();
            Core.inRange(hsvFrame, lowerBlue, upperBlue, mask);

            // 显示原始帧和提取的前景（使用掩码图像）
            HighGui.imshow(windowName, frame);
            HighGui.imshow("Mask", mask);

            // 处理前景（例如标记、跟踪等）
            // ...

            // 等待按键，以实现实时显示或保存结果图像等操作
            int key = HighGui.waitKey(30); // 等待30毫秒或直到用户按下按键为止
            if (key == 's') { // 如果用户按下's'键，保存当前帧和掩码图像
                String fileName = "frame_" + System.currentTimeMillis() + ".png";
                Imgcodecs.imwrite(fileName, frame);
                Imgcodecs.imwrite(fileName + "_mask.png", mask);
            } else if (key == 'q') { // 如果用户按下'q'键，退出程序
                break;
            }
        }

        // 释放VideoCapture对象和窗口资源
        capture.release();
        HighGui.destroyAllWindows();
    }
}
